package org.snlab.runtime;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;

import jdd.bdd.BDD;
import jdd.bdd.BDDIO;

public class BDDEngine {

    public BDD bdd;
    int[] vars;
    public final static int BDDFalse = 0;
    public final static int BDDTrue = 1;

    // bdd variable array is from high bit to low bit
    public BDDEngine(int size) {
        this.bdd = new BDD(1000, 1000);
        this.vars = new int[size];
        for (int i = 0; i < size; i++) {
            vars[i] = bdd.createVar();
        }
    }

    public synchronized int and(int x, int y) {
        int r = bdd.and(x, y);
        bdd.ref(r);
        return r;
    }

    public synchronized int or(int x, int y) {
        int r = bdd.or(x, y);
        bdd.ref(r);
        return r;
    }

    public synchronized int not(int x) {
        int r = bdd.not(x);
        bdd.ref(r);
        return r;
    }

    /**
     * substract y from x
     * @param x
     * @param y
     * @return
     */
    public synchronized int substract(int x, int y) {
        return and(x, not(y));
    }

    /**
     * check x is subset of y
     * @param x
     * @param y
     * @return
     */
    public synchronized boolean subset(int x, int y) {
        return bdd.and(x, y) == x;
    }

    public synchronized int encodeIP(Long ip, int prefix) {
        return this.encodeIP(BigInteger.valueOf(ip), prefix);
    }

    public synchronized int encodeIP(BigInteger ip, int prefix) {
        int result = BDDTrue;
        for (int i = 0; i < prefix; i++) {
            if (ip.testBit(31 - i)) {
                int tmp = and(result, vars[i]);
                // bdd.deref(result);
                result = tmp;
            } else {
                int tmp = not(vars[i]);
                int tmp1 = and(result, tmp);
                // bdd.deref(tmp);
                // bdd.deref(result);
                result = tmp1;
            }
        }
        return result;
    }

    public synchronized byte[] serialize(int x) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            BDDIO bddio = new BDDIO();
            bddio.save(this.bdd, x, bos);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return bos.toByteArray();
    }

    public synchronized int deserialize(byte[] bytes) {
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        int out = 0;
        try {
            BDDIO bddio = new BDDIO();
            out = this.bdd.ref(bddio.load(this.bdd, bis));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return out;
    }

}
